#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/time.h>
#include "zmalloc.h"

#include "list.h"					  /* use the SimCList library */
#include "utils.h"

#define TRUE 1
#define FALSE 0

typedef void *(*Shrunken) (void *,uint32_t);
typedef void *(*Boosting) (void *);

typedef union Ob {
   void **ptr;
   double *dptr;
   list *l;
} ob;


struct Pieces;

typedef struct _PBricks{

       void *global_parameters; 
       uint32_t begin,end;
       void **input;
       void *output;
       void *(*pfuncs)(void *);
       void *(*assign_addr)(void *,uint32_t);

       void *reward;

	   void 	*__null_used_pointer_for_align1;
       uint32_t max_tail;

} Pbricks;
        

typedef struct _Pieces {
       struct timeval tv; 

       pthread_t *threads;
	   pthread_mutex_t lock;

       unsigned int thread_count,pcount;
       
       const pthread_attr_t *attr;

       uint32_t len_of_mark,*mark,len_of_bricks;
       Pbricks **brick;
	   void 	*__null_used_pointer_for_align1;

       Boosting boosting;
       Shrunken shrunken;
} Pieces;

extern void *wait_all_2raw(void *,uint32_t) ;
extern void *boost_running(void *, list *, void *,Boosting,Shrunken,uint32_t) ;

void *wait_all_2raw(void *parptr, uint32_t timeout)
{
	Pieces *parallel;
	struct timeval tv;
	uint32_t i, Continue = 0;
	void *retval = NULL;

	long int diff = 0L;


	if ((parallel = (Pieces *) parptr) == NULL)
		return (retval);

	do {

		gettimeofday(&tv, NULL);

		diff = (tv.tv_usec + 1000000 * tv.tv_sec) - (parallel->tv.tv_usec + 1000000 * parallel->tv.tv_sec);

		if (diff > (long int)timeout * 1000000) {
	      pthread_mutex_lock(&parallel->lock);
            for ( i=0; i< parallel->thread_count; i++ ) {
            pthread_cancel(parallel->threads[i]);
            }
	      pthread_mutex_unlock(&parallel->lock);
		  break;
        }

		Continue = 0;
		for (i = 0; i < parallel->len_of_mark; i++)
			Continue += parallel->mark[i];

		if (Continue >= parallel->len_of_mark) {
	    pthread_mutex_lock(&parallel->lock);
        for ( i=0; i< parallel->thread_count; i++ ) {
             pthread_cancel(parallel->threads[i]);
            }
	    pthread_mutex_unlock(&parallel->lock);
          break;
        }


	    pthread_mutex_lock(&parallel->lock);
        if ( parallel->pcount != 0 ) {
        printf(" %s %d\n",__func__,Continue++);
	    pthread_mutex_unlock(&parallel->lock);
        continue;
        }

	    pthread_mutex_unlock(&parallel->lock);

	} while (0);

	if (parallel->brick == NULL) return (retval);

	pthread_mutex_lock(&parallel->lock);

	retval = parallel->brick[0]->output;

	if (parallel->threads!= NULL)
		zfree(parallel->threads);

	if (parallel->mark != NULL)
		zfree(parallel->mark);
    
	if ( parallel->brick != NULL ) {
    if ( parallel->brick[0] != NULL )
	    zfree(parallel->brick[0]);
    }
	if (parallel->brick)
	zfree(parallel->brick);

	pthread_mutex_unlock(&parallel->lock);
	
	zfree(parallel);

	return (retval);

}


void *boost_running(void *bFilter, list * glist, void *dst_addr, Boosting funcs, Shrunken sk_func, uint32_t timeout)
{

	Pieces *parallel;
	uint32_t i, groups, stride, last, Continue=0;
	void *tmplist = NULL;
	void **tmpaddr=NULL;

	Pbricks *bptr;

	do {
		if (funcs == NULL)
			break;
		if (glist == NULL)
			break;
		if (listLength(glist) == 0)
			break;
		if ((parallel = zmalloc(sizeof(Pieces))) == NULL) 
			break;
		if ((parallel->mark = zmalloc((listLength(glist)+1) *sizeof(uint32_t)) ) == NULL) {
			zfree(parallel);
			parallel = NULL;
			break;
		}
	
	pthread_mutex_init(&parallel->lock, NULL);
        parallel->pcount = 0;

		last = parallel->len_of_mark = listLength(glist);

		parallel->shrunken = sk_func;
		groups = core_count();

		do {
			stride = (parallel->len_of_mark + groups - 1) / groups;
			if (stride < 2) groups = (groups + 1) / 2;
		} while ((stride < 2) && (groups > 1));
        parallel->thread_count = groups;

		tmpaddr = (void **)listmap2vectoraddr(glist);
		gettimeofday(&parallel->tv, NULL);

		if ( ( parallel->brick = (Pbricks **) zmalloc((groups + 1) 
                    * sizeof(Pbricks *)) ) == NULL) break;

	    if ( (parallel->brick[0] = zmalloc( (groups + 1)*sizeof(Pbricks ) ) )
                == NULL ) break; 
		bptr = parallel->brick[0];
		bptr->max_tail = bptr->begin = bptr->end = last ;

		parallel->len_of_bricks = groups + 1;

		for (i = 1; i <= groups; i++) {
			parallel->brick[i] = parallel->brick[i - 1] + 1;
			bptr = parallel->brick[i];
			bptr->max_tail = bptr->begin = bptr->end = last ;
        }
        
        parallel->threads = zmalloc(groups*sizeof(pthread_t));

		for (i = 0; i <= groups; i++) {

			bptr = parallel->brick[i];

			bptr->max_tail = last;

			bptr->global_parameters = (void *)bFilter;
			bptr->begin = i * stride;
			if (bptr->begin >= last) {
				break;
			}

			bptr->end = (i + 1) * stride;
			if (bptr->end > last)
				bptr->end = last;

			bptr->input = tmpaddr;
			bptr->output = dst_addr;
			bptr->reward = (void *)parallel;
			bptr->pfuncs = funcs;
		
	    pthread_mutex_lock(&parallel->lock);
	    pthread_create(&parallel->threads[i],NULL,funcs, (void *)bptr);
            parallel->pcount++;
            printf("b:%d\t",parallel->pcount);
	    pthread_mutex_unlock(&parallel->lock);

		}
    
      printf("\n"); 
	 while( pthread_mutex_trylock(&parallel->lock) == 0 ) {
        if ( parallel->pcount != 0 ) {
	        printf("s:%4d  ",Continue++);
		    pthread_mutex_unlock(&parallel->lock);
	        continue;
	      }
	    pthread_mutex_unlock(&parallel->lock);
     } 

	} while (0);

	if ( (sk_func != NULL) && (parallel != NULL))
		tmplist = parallel->shrunken(parallel, timeout);

    if ( tmpaddr != NULL ) free(tmpaddr);

	return (tmplist);
}

#define Calloc(n, t)   (t *) calloc( (size_t) (n), sizeof(t) )

void *poutlist(void *para) {

	Pieces *parallel;
	Pbricks *bptr = (Pbricks *)para;
    int ncount,i,j,*xptr,*result;
    listNode **node_ptr;
    list *oxlist;

    do {

        if ( bptr == NULL ) break;
		if ( bptr->input == NULL)  break;

		if ( bptr->output== NULL)  break;

        ncount = *(int *)bptr->global_parameters;

        node_ptr = (listNode **)bptr->input;
        oxlist = (list *)bptr->output;
        for ( i=bptr->begin; i<bptr->end; i++) {
                xptr = listNodeValue((listNode *)node_ptr[i]);
                result = Calloc(1,int);
                for ( j=0 ; j<ncount; j++) {
                    *result += xptr[j];
                }
                listAddNodeTail(oxlist,result);
                printf("\nn %d\t.%d done\n",ncount,*result);
        }

    } while(0);

    while ( bptr != NULL ) {
	    if ( ( parallel = (Pieces *)bptr->reward ) == NULL ) break;
		pthread_mutex_lock(&parallel->lock);
	    printf(" , ");
	    if ( parallel->pcount  > 0 ) parallel->pcount--;
		pthread_mutex_unlock(&parallel->lock);
        break;
    }

    return(NULL);

};

int compdec(const void *a,const void *b) {
	return( (*(int *)a < *(int *)b)?1:0 );
}

int compinc(const void *a,const void *b) {
	return( (*(int *)a > *(int *)b)?1:0 );
}


int main(int argc,char **argv)
{
	struct timeval tmv;
	list *mylist,*outlist;					  /* declare a list */

	int i,j,*rnum;
	
	listIter *iter;
	listNode  *node;

	int ncount=10,opt,x,groups=8;
    long int mxint;

	while ((opt = getopt(argc, argv, "n:g:")) != -1) {
		switch (opt) {
		case 'n':
			ncount = atoi(optarg);
            break;
		case 'g':
			groups = atoi(optarg);
            break;
        default :
            break;
        }
   }

   if ( argc < 2 ) { 
           printf("%s -n [num]\n",argv[0]);
           exit(0);
   }

	mylist = list_create();		  /* initialize the list */
	mylist->free = free;
    mylist->sort_threads = 4;

    mxint = (ncount<10000)?(ncount*100):100000;
    mxint = 100;

	memset(&tmv,0x0,sizeof(struct timeval));

	printf("generate random %10d integer num max %lu",ncount,mxint);
	for (i = 0; i<ncount ; i++) {
		rnum = calloc(groups,sizeof(int));
        for( j=0 ; j<groups; j++)
		  rnum[j] = randi(mxint);
		listAddNodeTail(mylist, rnum);	/* add an element to the list */
		}
	printf("\n");

	printf("The list now holds %u elements.\n", listLength(mylist));	/* get the size of the list */
	tic(&tmv);
	
	list_qsort(mylist,compinc);

	toc(&tmv);

    outlist = list_create();
    outlist->free = free;
    boost_running((void *)&groups, mylist, (void *)outlist, poutlist, wait_all_2raw, 10);
    i=0;
    list_each_from_head(outlist,iter,node) {
        printf("%d x %d\n",++i,*(int *)listNodeValue(node));
           } list_each_end(iter);

	printf("\nfirst 50 is ");
    
	i = 0;
    list_each_from_head(mylist,iter,node) {
		;;
		if(++i < 50 ) 
		printf("%8u  ", *(int *)node->value);
	} list_each_end(iter);

	printf("\nlast 50 is ");
    
	i = 0;
	list_each_from_tail(mylist,iter,node) {
		;;
		printf("%8u  ", *(int *)node->value);
		if(++i > 50 ) break;

	} list_each_end(iter);

	printf("\n");

	printf("\nlistLength %d i %d \n",listLength(mylist),i);

	list_release(mylist);

	return 0;
}
